package com.iwechat.api.mina;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;

import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.mina.core.future.WriteFuture;
import org.apache.mina.core.session.IoSession;
import org.springframework.beans.factory.annotation.Autowired;

import com.alibaba.fastjson.JSONObject;
import com.wctm.adapter.mina.entity.Active;
import com.wctm.adapter.mina.entity.Exit;
import com.wctm.adapter.mina.entity.Submit;
import com.wctm.adapter.mina.entity.SubmitResp;
import com.wctm.emuns.ConstEmus;

/**
 * 客户端调用API
 *
 * 
 */
public class SocketAPI {
    private static final Logger logger      = LogManager.getLogger();
    private static final String CURR_WINDOW = "_currWindow?_";

    private String              charset;
    private Charset             charSet;

    @Resource(name = NioClientHandlerAdapter.RESPOOL_BEAN_NAME)
    private List<SubmitResp>    respPool;
    @Autowired
    private NioClient           client;

    private JSONObject          jsonTemplate;

    @PostConstruct
    public void init() {
        this.charSet = Charset.forName(charset);

        String localIp;
        try {
            localIp = InetAddress.getLocalHost().getHostAddress();
        } catch (UnknownHostException e) {
            localIp = "";
        }

        jsonTemplate = new JSONObject();
        jsonTemplate.put("clientIp", localIp);

        logger.info("### " + this + " init succeed!");
    }

    /**
     * <b>发送消息并返回该消息的流水号</b><br>
     * 异步发送。性能相对较高<br>
     * 流水号用于匹配消息响应报文.<br>
     * <br>
     * 建议的做法是：两个线程;一个线程负责发送消息，并将发送完毕的消息和得到的流水号缓存到内存中；另一个线程负责接收响应，将响应与缓存进行匹配<br>
     * 
     * <b>另外注意，一定不要和sendSyn()一起使用，否则会导致sendSyn()不能达到预期效果</b>
     *
     *
     * 
     * @param message
     * @return sequence
     * @throws Exception
     *             获取链接或发送过快
     */
    public int send(String message) throws Exception {
        IoSession session = client.getSession();
        if (session != null) {
            synchronized (session) {
                int curr = (Integer) session.getAttribute(CURR_WINDOW);
                if (curr > 0) {
                    session.getAttribute(CURR_WINDOW, curr--);
                    Submit sub = new Submit(message, charSet);
                    session.write(sub);
                    logger.info("send message,sequence[" + sub.getSequence() + "],content:" + message);
                    return sub.getSequence();
                } else {
                    throw new SendTooFastException();
                }
            }
        } else {
            throw new MinaConnectException();
        }
    }

    /**
     * <b>发送消息并返回该消息的流水号</b><br>
     * 
     * @see com.iwechat.api.mina.SocketAPI#send(String)
     * 
     * @param openid
     *            接收者openid
     * @param templateId
     *            服务方提供的模板编号
     * @param fields
     *            有序数组，顺序必须与服务方要求的顺序相同
     * @return
     * @throws Exception
     */
    public int send(String openid, String templateId, String[] fields, Map<String, String> urlParams) throws Exception {
        return this.sendWithTime(openid, templateId, fields, urlParams, "", "");
    }

    /**
     * <b>发送消息并返回该消息的流水号</b> <br>
     * 消息只可以在某个时间段内发送<br>
     * 
     * @see com.iwechat.api.mina.SocketAPI#send(String)
     * 
     * @param openid
     *            接收者openid
     * @param templateId
     *            服务方提供的模板编号
     * @param fields
     *            有序数组，顺序必须与服务方要求的顺序相同
     * @param startTime
     *            期望的消息开发发送时间 HHMMSS
     * @param stopTime
     *            期望的消息停止发送时间 HHMMSS
     * @return
     * @throws Exception
     */
    public int sendWithTime(String openid, String templateId, String[] fields, Map<String, String> urlParams, String startTime, String stopTime)
            throws Exception {
        int ret = 0;
        JSONObject message = packSingle(openid, templateId, fields, urlParams);
        message.put(ConstEmus.startTime.value(), StringUtils.leftPad(startTime, 6, '0'));
        message.put(ConstEmus.stopTime.value(), StringUtils.leftPad(stopTime, 6, '0'));
        ret = this.send(message.toJSONString());
        return ret;
    }

    /**
     * <b>发送消息并返回该消息发送结果</b><br>
     * 同步发送。性能相对较低<br>
     * 建议消息发送量不大的客户端调用,可以简化结果处理.<br>
     * <b>另外注意，一定不要和send()一起使用，否则会导致sendSyn()不能达到预期效果</b>
     * 
     * @param message
     * @return retCode<br>
     * 
     *         <pre>
     *         ERRORCODE_SUCCESS = 0;
     *         ERRORCODE_PKG_ERROR = 100;
     *         ERRORCODE_CONN_VERIFY_FAIL = 200;
     *         ERRORCODE_CONN_TOO_MANY = 201;
     *         ERRORCODE_SUB_TOO_FAST = 301;
     *         ERRORCODE_SUB_NO_LOGIN = 302;
     *         </pre>
     * 
     * @throws Exception
     *             获取链接或发送失败
     */
    private synchronized int sendSyn(String message) throws Exception {
        IoSession session = client.getSession();
        if (session != null) {
            synchronized (session) {
                int curr = (Integer) session.getAttribute(CURR_WINDOW);
                if (curr > 0) {
                    session.getAttribute(CURR_WINDOW, curr--);
                    Submit sub = new Submit(message, charSet);
                    WriteFuture wf = session.write(sub);
                    wf.awaitUninterruptibly();
                    logger.info("send message,sequence[" + sub.getSequence() + "],content:" + message);
                } else {
                    throw new SendTooFastException();
                }
            }
            SubmitResp resp = null;
            while (resp == null) {
                resp = this.recive();
            }
            return resp.getErrorCode();
        } else {
            throw new MinaConnectException();
        }
    }

    /**
     * <b>发送消息并返回该消息发送结果</b><br>
     * 
     * @see com.iwechat.api.mina.SocketAPI#sendSyn(String)
     * 
     * @param openid
     *            接收者openid
     * @param templateId
     *            服务方提供的模板编号
     * @param fields
     *            有序数组，顺序必须与服务方要求的顺序相同
     * @return
     * @throws Exception
     */
    public int sendSyn(String openid, String templateId, String[] fields, Map<String, String> urlParams) throws Exception {
        return this.sendSynWithTime(openid, templateId, fields, urlParams, "", "");
    }

    /**
     * <b>发送消息并返回该消息发送结果</b><br>
     * 消息只可以在某个时间段内发送<br>
     * 
     * @see com.iwechat.api.mina.SocketAPI#sendSyn(String)
     * 
     * @param openid
     *            接收者openid
     * @param templateId
     *            服务方提供的模板编号
     * @param fields
     *            有序数组，顺序必须与服务方要求的顺序相同
     * @param startTime
     *            期望的消息开发发送时间 HHMMSS
     * @param stopTime
     *            期望的消息停止发送时间 HHMMSS
     * @return
     * @throws Exception
     */
    public int sendSynWithTime(String openid, String templateId, String[] fields, Map<String, String> urlParams, String startTime, String stopTime)
            throws Exception {
        int ret = 0;
        JSONObject message = packSingle(openid, templateId, fields, urlParams);
        message.put(ConstEmus.startTime.value(), StringUtils.leftPad(startTime, 6, '0'));
        message.put(ConstEmus.stopTime.value(), StringUtils.leftPad(stopTime, 6, '0'));
        ret = this.sendSyn(message.toJSONString());
        return ret;
    }

    /**
     * 获取一条SubmitResp消息。<br>
     * 注意方法是异步的，也就是说，返回的resp对象并不是和send()方法发送的消息一一对应.<br>
     * 通过返回消息对象获取
     *
     * <pre>
     * resp.getSequence();
     * resp.getErrorCode();
     * </pre>
     *
     * 通过与发送消息得到的消息流水号进行匹配得到消息的发送结果。
     *
     * 
     * @return first message. may be null
     */
    public SubmitResp recive() {
        if (respPool.size() > 0) {
            logger.debug("pool size before recive:" + respPool.size());
            return respPool.remove(0);
        } else {
            return null;
        }
    }

    /**
     * 生成单条模板消息JSON报文
     * 
     * @param openid
     *            封装到 openid中
     * @param templateId
     *            封装到 templateId中
     * @param fields
     *            封装到 fields中
     * @param urlParams
     *            封装到 params中
     * @return 返回一个json是一个结果状态
     */
    private JSONObject packSingle(String openid, String templateId, String[] fields, Map<String, String> urlParams) {
        JSONObject json = (JSONObject) jsonTemplate.clone();
        if (fields != null && templateId != null) {
            json.put(ConstEmus.templateId.value(), templateId);
            json.put(ConstEmus.openid.value(), openid);
            JSONObject _fileds = new JSONObject(fields.length);
            json.put(ConstEmus.filedSize.value(), fields.length);
            json.put(ConstEmus.fileds.value(), _fileds);
            for (int i = 0; i < fields.length; i++) {
                _fileds.put(ConstEmus.filed_.value() + (i + 1), fields[i]);
            }
            if (urlParams != null && urlParams.size() > 0) {
                JSONObject paramJson = new JSONObject();
                for (Entry<String, String> e : urlParams.entrySet()) {
                    paramJson.put(e.getKey(), e.getValue());
                }
                json.put(ConstEmus.params.value(), paramJson);
            }
        }

        return json;
    }

    /**
     * 发送心跳报文<br>
     * 该方法一般不需要手工调用
     * 
     * 
     */
    public void active() {
        Active active = new Active();
        for (IoSession session : client.getSessionPool()) {
            if (session != null) {
                session.write(active);
            }
        }
    }

    /**
     * 发送退出报文<br>
     * 关闭连接前调用
     * 
     * 
     */
    public void exit() {
        Exit exit = new Exit();
        for (IoSession session : client.getSessionPool()) {
            if (session != null) {
                session.write(exit);
            }
        }
    }

    public void setCharset(String charset) {
        this.charset = charset;
    }
}
